﻿module Lexer
#light
let parse inputFile = seq{ yield inputFile }

open System
open FParsec.Primitives
open FParsec.CharParsers
open FParsec.Error

open Ast

let ws = spaces // skips any whitespace
let ch c =   skipChar c >>. ws
let str s =  pstring s .>> ws
let pSpace  = whitespace |>> ignore
let pSpacing = many pSpace

// Pragma???
let pPragma = (ch '#') >>. restOfLine .>> pSpacing |>> Pragma

// Strings
let notNewLine c = c <> '\r' && c <> '\n'
let notStrEnd c = notNewLine c && c <> '"'
let notBackSlash c = c <> '\\'
let pEsc = pipe2 (pchar '\\')  (satisfy notNewLine) (fun (x:char) (y:char) -> (string)x + (string)y)
let pStringString = between (ch '\"') (ch '\"' >>. pSpacing)
                     (manyStrings (pEsc <|> (satisfy (fun c -> notStrEnd c && notBackSlash c) |>> fun (c:char) -> (string)c)))
let pString = pStringString |>> String
let numberFormat =      NumberLiteralOptions.IncludeSuffixCharsInString
                    ||| NumberLiteralOptions.AllowSuffix
                    ||| NumberLiteralOptions.DefaultInteger
                    ||| NumberLiteralOptions.DefaultUnsignedInteger
                    ||| NumberLiteralOptions.DefaultFloat

// Numbers
let pNumber =
    numberLiteral numberFormat "number" .>> pSpacing
    |>> fun number ->
          if number.IsInteger then Integer (number.String)
          else Float(number.String)

// Identifier
let isIdentifierStart = fun c -> isAsciiLetter c || c = '_' || c = '$' // "A-Za-z_$"
let isIdentifierCont  = fun c -> isAsciiLetter c || isDigit c || c = '_' || c = '$' // A-Za-z_$0-9
let pIdentifierString = many1Satisfy2 isIdentifierStart isIdentifierCont
let pIdentifier = pIdentifierString .>> pSpacing |>> Identifier

// Operator
let pMult    = str "*"
let pPlus    = str "+"
let pMinus   = str "-"
let pDiv     = str "/"
let pMod     = str "%"
let pShLeft  = str "<<"
let pShRight = str ">>"

let pLEq     = str "<="
let pGEq     = str ">="
let pEquals  = str "=="
let pLess    = str "<"
let pGreater = str ">"
let pNotEq   = str "!="

let pNot = str "!"

let pSet = str "="

let pBinOperator = choice[pMult; pPlus; pMinus; pDiv; pMod; pShLeft; pShRight]
//let pOpSet = pipe2 pBinOperator (str "=") (fun x y -> x + y)
let pCompOperator = choice[pLEq; pGEq; pEquals; pGreater; pLess; pNotEq]

let pOperatorString = choice[pBinOperator; pCompOperator; pNot; pSet]
let pOperator = pOperatorString .>> pSpacing |>> Operator


// Symbol
let pSymbol = anyChar .>> pSpacing |>> Symbol

let pToken = choice [pPragma;
                     pString;
                     pIdentifier; 
                     pNumber;
                     pOperator;
                     pSymbol]

let pGrammar: Parser<_, unit> =
    pSpacing >>. many1 pToken .>> eof
